feat(workflow-executor): implement AgentPort adapter using agent-client#1496
Conversation
|
Coverage Impact This PR will not change total coverage. Modified Files with Diff Coverage (3)
🛟 Help
|
packages/workflow-executor/src/adapters/agent-client-agent-port.ts
Outdated
Show resolved
Hide resolved
packages/workflow-executor/src/adapters/agent-client-agent-port.ts
Outdated
Show resolved
Hide resolved
packages/workflow-executor/src/adapters/agent-client-agent-port.ts
Outdated
Show resolved
Hide resolved
Code reviewFound 1 issue:
🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
packages/workflow-executor/src/adapters/agent-client-agent-port.ts
Outdated
Show resolved
Hide resolved
| function extractRecordId( | ||
| primaryKeyFields: string[], | ||
| record: Record<string, unknown>, | ||
| ): Array<string | number> { | ||
| return primaryKeyFields.map(field => record[field] as string | number); | ||
| } |
There was a problem hiding this comment.
🟢 Low adapters/agent-client-agent-port.ts:30
extractRecordId uses a type assertion as string | number on record[field], but when the field is missing, this passes undefined through silently. When the resulting array is passed to encodePk, String(undefined) produces the literal string "undefined", corrupting the record ID. This is reachable in getRelatedData when the API response omits expected primary key fields, or when getCollectionRef defaults to ['id'] for an unknown collection but the actual records use a different key.
function extractRecordId(
primaryKeyFields: string[],
record: Record<string, unknown>,
): Array<string | number> {
- return primaryKeyFields.map(field => record[field] as string | number);
+ return primaryKeyFields.map(field => {
+ const value = record[field];
+ if (value === undefined || value === null) {
+ throw new Error(`Missing primary key field: ${field}`);
+ }
+ if (typeof value !== 'string' && typeof value !== 'number') {
+ throw new Error(`Invalid primary key type for ${field}: ${typeof value}`);
+ }
+ return value;
+ });
}🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file packages/workflow-executor/src/adapters/agent-client-agent-port.ts around lines 30-35:
`extractRecordId` uses a type assertion `as string | number` on `record[field]`, but when the field is missing, this passes `undefined` through silently. When the resulting array is passed to `encodePk`, `String(undefined)` produces the literal string `"undefined"`, corrupting the record ID. This is reachable in `getRelatedData` when the API response omits expected primary key fields, or when `getCollectionRef` defaults to `['id']` for an unknown collection but the actual records use a different key.
Evidence trail:
packages/workflow-executor/src/adapters/agent-client-agent-port.ts lines 25-33 (extractRecordId and encodePk functions), lines 73-84 (getRelatedData usage), lines 104-112 (getCollectionRef default to ['id']). JavaScript behavior: `String(undefined)` returns `"undefined"`.
Add AgentClientAgentPort class that wraps @forestadmin/agent-client's RemoteAgentClient to satisfy the AgentPort interface. Includes capabilities caching with rejection eviction, and full test coverage. fixes PRD-232 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Split RecordRef into CollectionRef (collection-level info: name, displayName, fields) and move recordId into RecordData. This fixes the type inconsistency where getCollectionRef() returned a RecordRef that had no meaningful recordId. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…elationship types - Create RecordNotFoundError in src/errors.ts for typed error handling - Check all 4 relationship types (ManyToOne, OneToOne, OneToMany, ManyToMany) using a RELATIONSHIP_TYPES Set instead of hardcoded ManyToOne check - Add parameterized tests for each relationship type Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… capabilities Replace runtime capabilities fetching with CollectionRef injected at construction time. This removes the capabilities cache, the HTTP dependency on _internal/capabilities, and simplifies the adapter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…actionEndpoints Add ActionRef type and actions field to CollectionRef. The adapter constructor now only takes client + collectionRefs, removing the separate actionEndpoints parameter and the dual source of truth. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ad of string[] Update AgentPort interface and adapter to return ActionRef[] (name + displayName) from getActions, making the display name available to consumers without a separate lookup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…yKeyFields Add primaryKeyFields to CollectionRef. buildPkFilter() generates a single Equal filter for simple PKs, or an And condition tree for composite PKs (separator: '|'). Fixes the hardcoded 'id' assumption. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… instead of pipe-encoded string Decouples the public API from the Forest Admin pipe encoding convention. Pipe encoding is now an internal implementation detail of AgentClientAgentPort only. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Callers no longer need to know PK field names — they pass values in primaryKeyFields order. Pipe encoding stays internal to encodePk(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6c861c5 to
2a2d898
Compare
…n.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…e.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cb8036b
into
feat/prd-214-setup-workflow-executor-package

Summary
AgentClientAgentPortclass wrapping@forestadmin/agent-client'sRemoteAgentClientto implement theAgentPortinterface@forestadmin/agent-clientas dependency of@forestadmin/workflow-executorKnown MVP limitations
displayName=fieldName(capabilities don't expose display names)referencedCollectionNameisundefined(not available in capabilities)relationNameascollectionName(no schema endpoint yet)Test plan
getRecord— returnsRecordData, throws on missing record, caches capabilities, retries after rejectionupdateRecord— returnsRecordData, callsupdate()with correct argsgetRelatedData— returnsRecordData[], handles empty resultsgetActions— reads fromactionEndpoints, returns[]for unknown collectionsexecuteAction— callsaction()thenexecute(), propagates errorsyarn workspace @forestadmin/workflow-executor test— 18 tests passingyarn workspace @forestadmin/workflow-executor lint— cleanyarn workspace @forestadmin/workflow-executor build— cleanfixes PRD-232
🤖 Generated with Claude Code
Note
Implement
AgentClientAgentPortadapter backed byagent-clientin workflow-executorAgentClientAgentPort, a newAgentPortimplementation that delegates toRemoteAgentClientfor record fetch, update, relation listing, and action execution.record.ts: introducesActionRefandCollectionRef, replacesRecordRef, and updatesRecordDatato carry compositerecordIdasArray<string|number>.AgentPortandWorkflowPortinterfaces to support composite primary keys and richer action data across all method signatures.RecordNotFoundErrorinerrors.tsthrown whengetRecordfinds no matching record.AgentPortmethod signatures now requireArray<string|number>for record IDs instead of scalar values; existing implementations must be updated.Changes since #1496 opened
RecordReftype withCollectionReftype in thePendingStepExecutioninterface'savailableRecordsproperty and updated corresponding import statement [2703ae7]@forestadmin/agent-clientdependency from production dependencies to a different dependency classification in@forestadmin/workflow-executorpackage [889ea3b]selectedRecordproperty type fromRecordDatatoCollectionRefinAiTaskStepExecutionDatainterface [e7fcf1b]Macroscope summarized 2a2d898.